Skip to content

fix(room-io): ownership-aware FrameProcessor lifecycle management#1277

Draft
toubatbrian wants to merge 1 commit intomainfrom
claude/jolly-lovelace-AZV5f
Draft

fix(room-io): ownership-aware FrameProcessor lifecycle management#1277
toubatbrian wants to merge 1 commit intomainfrom
claude/jolly-lovelace-AZV5f

Conversation

@toubatbrian
Copy link
Copy Markdown
Contributor

Summary

Automated port of livekit/agents#5467fix(room-io): ownership-aware FrameProcessor lifecycle management — into agents-js.

This is an automated Claude Code Routine created by @toubatbrian. Right now it is in experimentation stage.

What changed

In agents/src/voice/room_io/_input.ts (ParticipantAudioInputStream):

  1. New processorOwned flag — tracks whether the current frameProcessor was created internally by the stream (owned) vs. supplied externally by the caller.
  2. New private updateProcessor(processor) helper — ownership-aware replacement:
    • If processor === undefined and not owned → no-op (external processors survive transitions).
    • Else, if there is an owned predecessor different from the incoming processor → close() it.
    • Assigns the new processor and updates the processorOwned flag.
  3. closeStream() now calls updateProcessor(undefined) after clearing the input stream and publication. For externally-provided processors this is a safe no-op; if/when selector support is ever added, it will correctly dispose of selector-owned processors.
  4. close() still closes the processor unconditionally (matches Python's aclose() semantics), which handles the externally-provided case.
  5. New unit tests in _input.test.ts covering the four ownership invariants (noop on external, closes-and-clears on owned, replacement semantics, self-replacement noop).
  6. Changeset added as a @livekit/agents patch.

Each change carries a // Ref: python livekit-agents/livekit/agents/voice/room_io/_input.py - <lines> pointer as required by CLAUDE.md.

Implementation nuances (language-level differences from Python)

The Python PR introduces ownership tracking primarily to fix a bug specific to the selector callable noise-cancellation pattern, where _ParticipantAudioInputStream can be constructed with a callable that is invoked per-track to produce a fresh FrameProcessor. Those selector-produced processors are owned by the stream and must be closed on track switch / stream close — while externally-provided processors must survive track switches.

agents-js today does not support the selector callable pattern. Its noiseCancellation option is typed as NoiseCancellationOptions | FrameProcessor<AudioFrame> — always an external processor when a FrameProcessor is passed. Because of that:

  • The JS lifecycle was already partially correct: closeStream() never closed the processor; only close() did. No externally-provided processor was being prematurely disposed.
  • The selector bug fixed in Python does not manifest in the current JS code.
  • This PR therefore ports the pattern, not a behavior change for end users. The processorOwned flag is always false for processors provided via the constructor. The helper is structured so that if selector support is added later, only _create_stream-equivalent call sites need to call updateProcessor(newProcessor) with the new processor and the flag will flip on.

Not ported: selector callable support itself (NoiseCancellationSelector, NoiseCancellationParams). Those are larger API additions that precede the Python PR and are out of scope for this port.

File-by-file reference map

Python (livekit/agents @ #5467) JS (this PR) Notes
livekit-agents/livekit/agents/voice/room_io/_input.py (55-56) agents/src/voice/room_io/_input.ts (27-33) _processor_ownedprocessorOwned
livekit-agents/livekit/agents/voice/room_io/_input.py (172-181) agents/src/voice/room_io/_input.ts (65-79) _update_processorupdateProcessor
livekit-agents/livekit/agents/voice/room_io/_input.py (188) agents/src/voice/room_io/_input.ts (149-159) _close_stream uses _update_processor(None)
livekit-agents/livekit/agents/voice/room_io/_input.py (aclose) agents/src/voice/room_io/_input.ts (212-226) unconditional close + reset of ownership flag
tests/test_room_io.py (selector tests) — (not applicable, no selector API) Skipped
tests/test_room_io.py (direct processor lifecycle portions) agents/src/voice/room_io/_input.test.ts Simplified to a unit test of updateProcessor invariants

Test plan

  • pnpm build:agents — succeeds.
  • pnpm format:check — clean.
  • pnpm exec eslint on changed files — clean.
  • pnpm exec vitest run src/voice/room_io/_input.test.ts — 4/4 tests pass.
  • Integration: run an agent with an externally-provided FrameProcessor (e.g. AI-coustics plugin) against a LiveKit room, confirm it survives track republishes and is closed on agent shutdown.
  • Integration: confirm no change in behavior for NoiseCancellationOptions-only configurations.

Reviewers

cc @toubatbrian @livekit/agent-devs — please review the ported pattern and decide whether to hold this until selector-callable support is added to JS, or land it as a structural no-op now.

Port of livekit/agents#5467. Adds a `processorOwned` flag and an
internal `updateProcessor()` helper to `ParticipantAudioInputStream`
so externally-provided `FrameProcessor` instances survive track
transitions and are only closed on `close()`.

JS only accepts an external processor (no selector callable), so the
flag is always false for constructor-supplied processors; the
structural pattern is retained for symmetry with Python and in case
selector support is added later.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 20, 2026

🦋 Changeset detected

Latest commit: 7d0adfa

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 26 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants